 package emarket;

/*
 3APL Blockworld program by Jelle Herold, copyright 2003.
 Written for the Intelligent Systems group at Utrecht University.
 This is LGPL software.

 $Id: Env.java,v 1.7 2004/12/27 02:22:41 cvs-3apl Exp $
 */

// 2APL imports
import apapl.Environment;
import apapl.ExternalActionFailedException;
import apapl.data.*;

import java.lang.StringBuilder;
import java.util.logging.*;
import java.util.*;



import eis.exceptions.EntityException;
import eis.exceptions.ManagementException;
import eis.exceptions.NoEnvironmentException;
import eis.exceptions.RelationException;
import eis.iilang.EnvironmentCommand;
import eis.iilang.Function;
import eis.iilang.Numeral;
import eis.iilang.Parameter;


public class Env extends Environment
{

    private Logger _logger = Logger.getLogger("EMarket."+Env.class.getName());
   
    private List<Offer> 	_offers = new ArrayList<Offer>();
    private List<Request>	_requests = new ArrayList<Request>();
    
	// The default constructor
	public Env()
	{
		super();
	}

	/** EIS Environment Management  **/
    public void addAgentEntity(String agent) 
    {
        // If the number of entities has been specified in the MAS file, 
        // bypass 2APL specific automatic agent-entity attaching mechanism.
        //if (agentmap.containsKey("robot0")) return;
        
        super.addAgentEntity(agent);
    }
    

    /* Called from 2apl */
    //First argument must be the agent name of type String and is given underwater from 2apl.
    //APLIdent, APLNum etc. works for other arguments.
    
    public synchronized Term testAction1(String agentName)
    {
    	_logger.info("Performing testAction1" + agentName);
    	
    	return wrapBoolean(true);
    }
    
    public synchronized Term testAction2(String agentName, APLIdent var)
    {
    	_logger.info("Performing testAction2" + agentName + " var " + var.toString());
    	
    	return wrapBoolean(true);
    }
    
    public synchronized Term testWait( String agentName, APLNum time )
    {
    	try {
    		Thread.sleep( time.toInt() );
    	} catch( Exception e ) {
    		
    	}
    	return wrapBoolean(true);
    }
    
    /**
	 * Register a new request in the environment, a trader can only have 1 request.
	 * If the trader is allready offering the item, the method fails.
	 * @param itemName Name of the item
	 * @param buyer Name of the requesting Trader
	 * @return A boolean indicating whether this method was successful.
	 */
    public synchronized Term registerRequest(String agentName, APLIdent itemNameIdent, APLIdent buyerIdent) 
    throws ExternalActionFailedException
    {
    	String itemName = itemNameIdent.toString();
    	String buyer = buyerIdent.toString();
    	
    	_logger.info("Entered registerRequest(" + itemName + ", " + buyer + ").");
    	Request request = new Request( itemName, buyer  );
		
		 //If the trader is also offering the item, the method fails.
		if(_offers.contains(new Offer(itemName, buyer)))
		{
			throwExternalActionFailed("Trader " + buyer +  " is also offering item " + itemName);
		} 
		// If the request is not allready registered, register the item.
		if(!_requests.contains(request))
		{
			 if(!this._requests.add(request))
			 {
				 throwExternalActionFailed("Failed to add request to database");
			 }
		}
		else
		{
			throwExternalActionFailed("Request allready registered.");
		}
		
    	return wrapBoolean(true);
    }
    
    /**
	 * Register a new offer in the environment.
	 * @param seller Name of the offering Trader
	 * @param itemName Name of the item
	 * @return A list of matching requests (Buyers to inform a new offer is added).
	 */
    public synchronized Term registerOffer(String agentName, APLIdent itemNameIdent, APLIdent sellerIdent)
    throws ExternalActionFailedException
    {
    	String itemName = itemNameIdent.toString();
    	String seller = sellerIdent.toString();
    	
    	_logger.info("Entered registerOffer(" + itemName + ", " + seller + ").");
    	
    	Offer offer = new Offer( itemName, seller );
		
		boolean offerAdded = false;
		
		//If the trader is also requesting this item, the method fails.
		if(_requests.contains(new Request(itemName, seller)))
		{
			throwExternalActionFailed("Trader" + seller + " is also requesting item " + itemName);
		}
		//Otherwise the offer is added to the database.
		else
		{
			if(!_offers.add(offer))
				throwExternalActionFailed("Failed to add offer to database");
		}
		
		
		Set<Request> matchingRequests = getMatchingRequestsInt(itemName);
		LinkedList<Term> matchingRequestsTerms = new LinkedList<Term>();
		for(Request request : matchingRequests)
		{
			matchingRequestsTerms.add(new APLIdent(request.get_trader().get_identifier()));
		}
		APLList listOfBuyers = new APLList(matchingRequestsTerms);
		return listOfBuyers;
    }
    
    /**
	 * Removes an existing request
	 * @param itemName The Name of the item
	 * @param buyer The name of the buyer.
	 * @return A boolean indicating wether the request database no longer contains the request.
	 */
    public synchronized Term removeRequest(String agentName, APLIdent itemNameIdent, APLIdent buyerIdent)
    throws ExternalActionFailedException
    {
    	String itemName = itemNameIdent.toString();
    	String buyer = buyerIdent.toString();   	
    	
    	_logger.info("Entered removeRequest(" + itemName + ", " + buyer + ").");
    	    	
    	Request request = new Request(itemName, buyer);
		
		if(_requests.contains(request))
		{
			if(!_requests.remove(request))
			{
				throwExternalActionFailed("Failed to remove request");
			}
		}
		
		//Return true if the request didnt exists or was succesfully removed.
		return wrapBoolean(true);
    }

    /**
	 * Removes an existing offer
	 * @param itemName The name of the item.
	 * @param seller The name of the seller.
	 * @return A list of buyers that are requesting the offer.
	 */
    public synchronized Term removeOffer(String agentName, APLIdent itemNameIdent, APLIdent sellerIdent)
    throws ExternalActionFailedException
    {
    	String itemName = itemNameIdent.toString();
    	String seller = sellerIdent.toString();  	
    	
    	_logger.info("Entered removeOffer(" + itemName + ", " + seller + ").");
    	
    	Offer offer = new Offer(itemName, seller);
		
		boolean offerRemoved = false;
		boolean dbNotContainsOffer = true;
		
		if(_offers.contains(offer))
		{
			offerRemoved = _offers.remove(offer);
			dbNotContainsOffer = offerRemoved;
			
			if(!dbNotContainsOffer)
				throwExternalActionFailed("Failed to remove offer from database.");
			
		}
		
		LinkedList<Term> matchingRequestsTerms = new LinkedList<Term>();
		if(offerRemoved)
		{
			
			Set<Request> matchingRequests = getMatchingRequestsInt(itemName);
			for(Request request : matchingRequests)
			{
				matchingRequestsTerms.add(new APLIdent(request.get_trader().get_identifier()));
			}
		}
	
		
		APLList listOfBuyers = new APLList(matchingRequestsTerms);
		return listOfBuyers;
    }
    
    /**
     * Finds offers that match the given item.
     * @param itemName
     * @param buyer
     * @return A list of sellers that sell the given item.
     */
    public synchronized Term findMatchingOffers(String agentName, APLIdent itemNameIdent, APLIdent buyerIdent)
    throws ExternalActionFailedException
    {
    	String itemName = itemNameIdent.toString();
    	String buyer = buyerIdent.toString();
    	
    	_logger.info("Entered findMatchingOffers(" + itemName + ", " + buyer + ").");
    	  	    	
    	Set<Offer> matchingOffers = getMatchingOffersInt(itemName);
    	LinkedList<Term> matchingOffersTerms = new LinkedList<Term>();
		
		for(Offer offer : matchingOffers)
		{
			 matchingOffersTerms.add(new APLIdent(offer.get_trader().get_identifier()));
		}
								
		APLList matchingOffersList = new APLList(matchingOffersTerms);
		return matchingOffersList;
    }
    
    
    /* Methods used by 2apl accesible methods */
	
    /**
     * Finds offers that match the given item.
     * @param itemName
     * @param buyer
     * @return A boolean indicating whether this method was succesful.
     */
    private Set<Offer> getMatchingOffersInt(String itemName)
    {
    	Set<Offer> matchingOffers = new HashSet<Offer>();
    	Item item = new Item(itemName);
    	
    	for(Offer offer : _offers)
    	{
    		if(offer.get_item().equals(item))
    		{
    			matchingOffers.add(offer);
    		}
    	}	
    	return matchingOffers;
    }
    
    /**
     * Finds offers that match the given item.
     * @param itemName
     * @param buyer
     * @return A boolean indicating whether this method was succesful.
     */
    private Set<Request> getMatchingRequestsInt(String itemName)
    {
    	Set<Request> matchingRequests = new HashSet<Request>();
    	Item item = new Item(itemName);
    	
    	for(Request request : _requests)
    	{
    		if(request.get_item().equals(item))
    		{
    			matchingRequests.add(request);
    		}
    	}
        	
    	return matchingRequests;
    }
  
    
    
    
    
    /* Helper functions */
	static public APLListVar wrapBoolean( boolean b )
	{
		return new APLList(new APLIdent(b ? "true" : "false"));
	}

    
    
    
    public void throwExternalActionFailed (String message ) throws ExternalActionFailedException
    {
    	_logger.info( message );
    	throw new ExternalActionFailedException( message );
    }
    
    
    
}
